package main

import (
	"database/sql"
	"errors"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/xorm"
)

// Bank account
type Account struct {
	Id      int64
	Name    string `xorm:"unique"`
	Balance float64
	Version int `xorm:"version"` // Optimistic Locking
}

// ORM engine
var x *xorm.Engine

func db_prepare(dbname string) {
	db, err := sql.Open("mysql", "${usr}:${pwd}@tcp(localhost:43306)/?charset=utf8")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	_, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbname)
	if err != nil {
		panic(err)
	}
}

func db_sync(dbname string) {
	// Create ORM engine and database
	var err error
	addr := fmt.Sprintf("${usr}:${pwd}@tcp(localhost:43306)/%s?charset=utf8", dbname)
	x, err = xorm.NewEngine("mysql", addr)
	if err != nil {
		log.Fatalf("Fail to create engine: %v\n", err)
	}

	//x.ShowSQL(true)
	// Sync tables
	if err = x.Sync(new(Account)); err != nil {
		log.Fatalf("Fail to sync database: %v\n", err)
	}
}

func newAccount(name string, balance float64) error {
	_, err := x.Insert(&Account{Name: name, Balance: balance})
	return err
}

func getAccount(id int64) (*Account, error) {
	a := &Account{}
	has, err := x.Id(id).Get(a)
	if err != nil {
		return nil, err
	} else if !has {
		return nil, errors.New("Account does not exist")
	}
	return a, nil
}

func makeDeposit(id int64, deposit float64) (*Account, error) {
	a, err := getAccount(id)
	if err != nil {
		return nil, err
	}
	a.Balance += deposit
	_, err = x.Id(a.Id).Update(a)
	return a, err
}

func makeWithdraw(id int64, withdraw float64) (*Account, error) {
	a, err := getAccount(id)
	if err != nil {
		return nil, err
	}
	if a.Balance < withdraw {
		return nil, errors.New("Not enough balance")
	}
	a.Balance -= withdraw
	_, err = x.Update(a)
	return a, err
}

func makeTransfer(id1, id2 int64, balance float64) error {
	a1, err := getAccount(id1)
	if err != nil {
		return err
	}

	a2, err := getAccount(id2)
	if err != nil {
		return err
	}

	if a1.Balance < balance {
		return errors.New("Not enough balance")
	}

	// Following code should be improved by transactions
	a1.Balance -= balance
	a2.Balance += balance
	if _, err = x.Update(a1); err != nil {
		return err
	} else if _, err = x.Update(a2); err != nil {
		return err
	}

	return nil
}

func getAccountsAscId() (as []Account, err error) {
	// Find returns all results
	err = x.Find(&as)
	return as, err
}

func getAccountsDescBalance() (as []Account, err error) {
	err = x.Desc("balance").Find(&as)
	return as, err
}

func deleteAccount(id int64) error {
	_, err := x.Delete(&Account{Id: id})
	return err
}

func main() {
	db_prepare("test")
	db_sync("test")
	if err := newAccount("Allen", 1000); err != nil {
		println("new account failed")
	}
	if err := newAccount("Dobe", 2000); err != nil {
		println("new account failed")
	}

	as, err := getAccountsAscId()
	if err == nil {
		for _, a := range as {
			fmt.Printf("%v\n", a)
			if _, err2 := makeDeposit(a.Id, 200); err2 != nil {
				fmt.Printf("%v\n", err2)
			}
		}
	}

	as, err = getAccountsDescBalance()
	if err == nil {
		for _, a := range as {
			fmt.Printf("%v\n", a)
		}
	}
}
